﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GrayParrot.Core.Data;

namespace GrayParrot.Data.EntityFramework
{
    /// <summary>
    /// http://www.achadwick.com/DeveloperBlog/Result/Entity-Framework-DbContext-Wrapper
    /// </summary>
    /// <typeparam name="TContext">The type of the context.</typeparam>
    public class DbContext<TContext> : IDbContext<TContext> where TContext : DbContext, new()
    {
        protected IDbConnection _connection;
        private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
        private readonly LinkedList<UnitOfWork> _workItems = new LinkedList<UnitOfWork>();
        private DbContext m_context = null;

        public virtual TContext BuildContext(bool enableChangeTracking = false, bool enableLazyLoading = false, bool enableProxyCreation = false)
        {
            if (m_context != null) return (TContext)m_context;

            var result = new TContext();
            result.Configuration.AutoDetectChangesEnabled = enableChangeTracking;
            result.Configuration.LazyLoadingEnabled = enableLazyLoading;
            result.Configuration.ProxyCreationEnabled = enableProxyCreation;

            m_context = result;
            _connection = result.Database.Connection;

            return result;
        }

        public IUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            if(m_context == null)
              m_context = BuildContext();

            _connection = m_context.Database.Connection;

            //To create a transaction, our connection needs to be open.
            //If we need to open the connection ourselves, we're also in charge of closing it when this transaction commits or rolls back.
            //This will be done by RemoveTransactionAndCloseConnection in that case.
            bool wasClosed = _connection.State == ConnectionState.Closed;
            if (wasClosed) _connection.Open();

            try
            {
                UnitOfWork unit;
                IDbTransaction transaction = _connection.BeginTransaction(isolationLevel);

                unit = new UnitOfWork(m_context);

                _rwLock.EnterWriteLock();
                _workItems.AddLast(unit);
                _rwLock.ExitWriteLock();

                return unit;
            }
            catch
            {
                //Close the connection if we're managing it, and if an exception is thrown when creating the transaction.
                if (wasClosed) _connection.Close();

                throw; //Rethrow the original transaction
            }
        }

        public void Dispose()
        {
            
        }

        public void ErrorHandler(Exception ex)
        {
            
        }
















        #region Extra Methods...
        public virtual T Call<T>(Func<TContext, T> func)
        {
            using (var context = this.BuildContext())
            {
                return func(context);
            }
        }

        public virtual IEnumerable<T> Call<T>(Func<TContext, IEnumerable<T>> func)
        {
            using (var context = this.BuildContext())
            {
                return func(context).ToList();
            }
        }

        public virtual void Call(Action<TContext> action)
        {
            using (var context = this.BuildContext())
            {
                action(context);
            }
        }

        public virtual async Task<T> CallAsync<T>(Func<TContext, Task<T>> func)
        {
            using (var context = this.BuildContext())
            {
                return await func(context).ConfigureAwait(false);
            }
        }

        public virtual async Task CallAsync(Func<TContext, Task> func)
        {
            using (var context = this.BuildContext())
            {
                await func(context).ConfigureAwait(false);
            }
        }

        public virtual T CallWithTransaction<T>(Func<TContext, T> func, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            using (var context = this.BuildContext())
            {
                using (var tx = context.Database.BeginTransaction(isolationLevel: isolationLevel))
                {
                    var result = func(context);
                    tx.Commit();
                    return result;
                }
            }
        }

        public virtual void CallWithTransaction(Action<TContext> action, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            using (var context = this.BuildContext())
            {
                using (var tx = context.Database.BeginTransaction(isolationLevel: isolationLevel))
                {
                    action(context);
                    tx.Commit();
                }
            }
        }

        public virtual async Task<T> CallWithTransactionAsync<T>(Func<TContext, Task<T>> func, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            using (var context = this.BuildContext())
            {
                using (var tx = context.Database.BeginTransaction(isolationLevel: isolationLevel))
                {
                    var result = await func(context).ConfigureAwait(false);
                    tx.Commit();
                    return result;
                }
            }
        }

        public virtual async Task CallWithTransactionAsync(Func<TContext, Task> func, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            using (var context = this.BuildContext())
            {
                using (var tx = context.Database.BeginTransaction(isolationLevel: isolationLevel))
                {
                    await func(context).ConfigureAwait(false);
                    tx.Commit();
                }
            }
        }
        #endregion
    }
}
